﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Effects.Effects.Tools;
using Effects.Effects.GuiParts;

namespace Effects.Effects
{
    /// <summary>
    ///  Tremolo effect implementation.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   Tremolo is quite simple effect. It controls the level of input signal accordingly to oscillation of LFO and mixes it with dry input signal.
    ///  </para>
    /// </remarks>
    public class Tremolo : Effect
    {
        #region controls
        // fields and properties for user controlled parameters 

        #region speed
        // speed control

        /// <summary>
        ///  Controls period of the LFO.
        /// </summary>
        private Pot speed;
        /// <summary>
        ///  Key for the speed parameter
        /// </summary>
        private const int speedKey = 1;

        #endregion

        #region depth
        // depth parameter

        /// <summary>
        ///  Controls depth - dry/wet mix
        /// </summary>
        private Pot depth;
        /// <summary>
        ///   Current dry/wet mix.
        /// </summary>
        private double depthValue;

        /// <summary>
        ///  Key for the depth parameter.
        /// </summary>
        private const int depthKey = 2;

        #endregion

        #region oscillator_switching
        // oscillation type switching

        /// <summary>
        ///  Oscillator controling the volume level
        /// </summary>
        private Oscillator oscillator;

        /// <summary>
        ///  Key for the oscillation type parameter.
        /// </summary>
        private const int oscillatorKey = 3;

        /// <summary>
        ///  Oscillation type. 0 - sinwave, 1 - squarewave
        /// </summary>
        private int oscillationType=0;


        /// <summary>
        ///  Switches among different oscillation types.
        /// </summary>
        private Switch OscillationSwitch;

        #endregion

        #region footswitch

        /// <summary>
        ///  Footswitch for by passing effect.
        /// </summary>
        private Footswitch footswitch;

        /// <summary>
        ///  Led indicating On/Off state.
        /// </summary>
        private LED led;

        /// <summary>
        ///  Synchornizer for footswitch and led.
        /// </summary>
        private FootswitchLedSynchronizer synchronizer;


        /// <summary>
        ///  Key for footswitch settings.
        /// </summary>
        private int footswitchKey = 0;

        #endregion

        #endregion

        #region effect_properties
        // overridden preperties of base class Effect

        /// <summary>
        ///  Time interval between two adjecant samples in miliseconds.
        /// </summary>
        private double step;

        /// <summary>
        ///  Gets or sets the sampling rate.
        /// </summary>
        public override uint SampleRate
        {
            get
            {
                return base.SampleRate;
            }
            set
            {
                base.SampleRate = value;
                this.step = 1000 / (double) sampleRate;
            }
        }

        /// <summary>
        ///  Gets the name fo the effect.
        /// </summary>
        public override string Name
        {
            get
            {
                return "Tremolo v2.0";
            }
        }
        #endregion

        #region constructor
        // constructor and initialization stuff

        /// <summary>
        ///  Creates new instace of the Tremolo with initial settings.
        /// </summary>
        public Tremolo():base()
        {
            
            this.BackgroundImage = System.Drawing.Image.FromStream(new System.IO.MemoryStream(global::Effects.Recources.EffectBackgorunds.Tremolo));
            this.Size = this.BackgroundImage.Size;

            oscillator = new SinOscilator();
            InitializeComponents();
            InitializeFootswitch();
            this.SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint |
                          System.Windows.Forms.ControlStyles.UserPaint |
                          System.Windows.Forms.ControlStyles.DoubleBuffer, true);
        }

        /// <summary>
        ///  Initializes the pots and the switch.
        /// </summary>
        private void InitializeComponents()
        {
            this.speed = new Pot();
            this.speed.MaxValue = 1000;
            this.speed.MinValue = 30;
            this.speed.Location = new System.Drawing.Point(3, 38);
            speed.Knob = PotKnob.ChickenKnobMiddle;
            this.speed.ValueChanged += this.SpeedPotChanged;

            this.depth = new Pot();
            this.depth.MaxValue = 1;
            this.depth.MinValue = 0;
            this.depth.Location = new System.Drawing.Point(75, 38);
            depth.Knob = PotKnob.ChickenKnobMiddle;
            this.depth.ValueChanged += this.DepthPotChanged;


            this.OscillationSwitch = new Switch(2);
            this.OscillationSwitch.Location = new System.Drawing.Point(60, 110);
            this.OscillationSwitch.Height = 20;
            this.OscillationSwitch.Width = 30;
            this.OscillationSwitch.OnSwitch += new SwitchSwitched(this.OscillationSwitched);
            this.OscillationSwitch.SwitchKnobColor = System.Drawing.Color.FromArgb(0, 128, 255);

            this.Controls.Add(speed);
            this.Controls.Add(depth);
            this.Controls.Add(OscillationSwitch);
            speed.Reset();
            depth.Reset();
        }

        /// <summary>
        ///  Iniializes footswitch and led.
        /// </summary>
        private void InitializeFootswitch()
        {
            // footswitch
            footswitch = new Footswitch();
            switchedOn = true;
            footswitch.On = true;
            footswitch.Switched += new FootswitchSwitchedEventHandler(this.FootswitchSwitched);
            footswitch.Location = new System.Drawing.Point(60, 145);
            Controls.Add(footswitch);

            // led
            led = new LED();
            led.On = true;
            led.Location = new System.Drawing.Point(65,30);
            Controls.Add(led);

            // synchronization
            synchronizer = new FootswitchLedSynchronizer(footswitch,led);
        }

        #endregion

        #region effect_method
        // processing signal method

        /// <summary>
        ///  Effects input signal.
        /// </summary>
        /// <param name="data"> Input signal </param>
        /// <returns> Modified ouput </returns>
        public override double[] effect(double[] data)
        {

            if (switchedOn)
            {
                double[] outData = new double[data.Length];


                for (int i = 0; i < data.Length; i++)
                {
                    outData[i] = (1 - depthValue) * data[i] + depthValue * data[i] * oscillator.Step(step);
                }


                return outData;
            }

            return data;
        }

        #endregion

        #region controls_event_handlers
        // handling events from controls

        /// <summary>
        ///  Speed pot event handler.
        /// </summary>
        /// <param name="sender"> Pot that has raised the event </param>
        /// <param name="e"> Event arguments </param>
        private void SpeedPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.oscillator.Period = e.NewValueInt;
            if (SettingsChanged != null)
            {
                EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(speedKey, e.NewValueInt);
                SettingsChanged(this, settings);
            }
           
        }

        /// <summary>
        ///  Depth pot event handler.
        /// </summary>
        /// <param name="sender"> Pot that has raised the event </param>
        /// <param name="e"> Event arguments </param>
        private void DepthPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.depthValue = e.NewValue;
            if (SettingsChanged != null)
            {
                EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(depthKey, e.NewValue);
                SettingsChanged(this, settings);
            }
        }

        /// <summary>
        ///  OscillationSwitch event handler
        /// </summary>
        /// <param name="sender"> Switch that raised the event </param>
        /// <param name="args"> Event arguments </param>
        private void OscillationSwitched(object sender, SwitchEventArgs args)
        {
            SetOscillator(args.State);
            if (SettingsChanged != null)
            {
                EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(oscillatorKey, oscillationType);
                SettingsChanged(this, settings);
            }
        }

        /// <summary>
        ///  Method sets oscillator according to the given value.
        /// </summary>
        /// <param name="oscillationType"> Type of the oscillator </param>
        private void SetOscillator(int oscillationType)
        {
            this.oscillationType = oscillationType;
            double period = this.oscillator.Period;
            switch (oscillationType)
            {
                case 0:
                    oscillator = new SinOscilator();
                    break;
                case 1:
                    oscillator = new SquareOscilator();
                    break;
            }
            oscillator.Period = period;
        }

        /// <summary>
        ///  Footswitch.Switched event handler. According to the event arguments signal bypasses or runs through the effect.
        /// </summary>
        /// <param name="sender"> Object that has raised the event. </param>
        /// <param name="args"> Event Arguments </param>
        private void FootswitchSwitched(object sender, FootswitchSwitchedEventArgs args)
        {
            this.switchedOn = args.On;

            // raise the event
            if (this.SettingsChanged != null)
            {
                EffectSettingsChangedEventArgs changeArgs = new EffectSettingsChangedEventArgs(footswitchKey, switchedOn);
                this.SettingsChanged(this, changeArgs);
            }
        }

        #endregion

        #region settings
        // getting and setting the settings of Tremolo

       /// <summary>
       ///   Raised when any parameter of effect is changed. 
       /// </summary>
        public override event EffectSettingsChangedDelegate SettingsChanged;

        /// <summary>
        ///  Gets all current settings of the effect.
        /// </summary>
        /// <returns> All current seetings </returns>
        public override object[] GetAllSettings()
        {
            object[] settings = new object[4];

            settings[0] = switchedOn;
            settings[1] = Convert.ToInt32(oscillator.Period);
            settings[2] = depthValue;
            settings[3] = this.oscillationType;


            return settings;
        }

        /// <summary>
        ///  Sets all settings.
        /// </summary>
        /// <param name="settings"> All settings of the effect. This argument should be output of GetAllSettings method that has been called previously. </param>
        public override void SetAllSettings(object[] settings)
        {
            object[] settingsArray = (object[])settings;

            this.switchedOn = (bool)settingsArray[0];
            synchronizer.SetValue(switchedOn);
            
            this.depthValue = (double)settingsArray[2];
            depth.SetValue(depthValue);

            SetOscillator((int)settingsArray[3]);
            OscillationSwitch.State = (int)settingsArray[3];

            oscillator.Period = (int)settingsArray[1];
            speed.SetValue(Convert.ToInt32((int)settingsArray[1]));
        }

        /// <summary>
        ///  Sets one settings
        /// </summary>
        /// <param name="e"> Setting change to be done </param>
        public override void SetSetting(EffectSettingsChangedEventArgs e)
        {
            int key = (int)e.Key;
            if (key == footswitchKey)
            {
                this.switchedOn = (bool)e.Value;
                synchronizer.SetValue(switchedOn);
                return;
            }

            if (key == depthKey)
            {
                this.depthValue = (double)e.Value;
                depth.SetValue(depthValue);
                return;
            }

            if (key == speedKey)
            {
                oscillator.Period = (int)e.Value;
                speed.SetValue((int)e.Value);
                return;
            }

            if (key == oscillatorKey)
            {
                SetOscillator((int)e.Value);
                OscillationSwitch.State = oscillationType;
            }
                
        }

        /// <summary>
        ///  Return array desribing all parameters.
        /// </summary>
        /// <returns> Array describing parameters </returns>
        public override EffectParam[] GetParameters()
        {
            EffectParam[] parameters = new EffectParam[4];

            ListItem[] footswitchState = new ListItem[2];
            footswitchState[0] = new ListItem("Turn on", true);
            footswitchState[1] = new ListItem("Turn off", false);
            parameters[0] = new EffectParam(footswitchKey, "ON/OFF", footswitchState);

            EffectParam speedParam = new EffectParam(speedKey, "Speed", 30, 1000);
            EffectParam depthParam = new EffectParam(depthKey, "Depth", 0F, 1F);
            parameters[1] = speedParam;
            parameters[2] = depthParam;

            ListItem[] oscillationTypes = new ListItem[2];
            oscillationTypes[0] = new ListItem("Sinwave oscillation", 0);
            oscillationTypes[1] = new ListItem("Squarewave oscillation", 1);
            parameters[3] = new EffectParam(oscillatorKey, "Oscillation type", oscillationTypes);

            return parameters;
        }

        #endregion
    }
}
